package com.xiyuan.smartutils.client.ws.core;

import com.xiyuan.smartutils.client.ws.WebSocketConstants;

import java.nio.ByteBuffer;

public class WebsocketPacketHeader implements WebSocketConstants {
    /**********************************************/
    // the packet header
    
    /**********************************************/
    
    /**
     * The header length.
     */
    protected final int headerLength;
    /**
     * The payload length.
     */
    protected final long payloadLength;
    
    /**
     * The payload length type.
     */
    protected final PayloadLengthType payloadLengthType;
    /**
     * The fragmented. a middle packet
     */
    protected final boolean fragmented;
    /**
     * the opcode
     */
    protected Opcode opcode;
    
    protected boolean mask;
    
    public Opcode getOpcode() {
        return opcode;
    }
    
    public boolean isMask() {
        return mask;
    }
    
    public void setMask(boolean mask) {
        this.mask = mask;
    }
    
    public boolean isFragmented() {
        return fragmented;
    }
    
    public boolean isPing() {
        return Opcode.PING.equals(opcode);
    }
    
    public boolean isPong() {
        return Opcode.PONG.equals(opcode);
    }
    
    public boolean isClose() {
        return Opcode.CLOSE.equals(opcode);
    }
    
    public int getHeaderLength() {
        return headerLength;
    }
    
    public long getPayloadLength() {
        return payloadLength;
    }
    
    public PayloadLengthType getPayloadLengthType() {
        return payloadLengthType;
    }
    
    private WebsocketPacketHeader(boolean fragmented, Opcode opcode, int headerLength, long payloadLength,
                                  PayloadLengthType payloadLengthType) {
        this.headerLength = headerLength + payloadLengthType.offset();
        this.payloadLengthType = payloadLengthType;
        this.payloadLength = payloadLength;
        this.fragmented = fragmented;
        this.opcode = opcode;
    }
    
    public static WebsocketPacketHeader parseHeader(boolean fragmented, Opcode opcode, int headerLength,
                                                    long payloadLen,
                                                    PayloadLengthType payloadLenType) {
        return new WebsocketPacketHeader(fragmented, opcode, headerLength, payloadLen, payloadLenType);
    }
    
    public static WebsocketPacketHeader parseHeader(ByteBuffer chunkData, WebsocketPacketHeader prevHeader) {
        if (chunkData == null) {throw new IllegalArgumentException("Data is null.");}
        
        int length = chunkData.limit() - chunkData.position();
        if (length < 2) {return null;}
        
        // 1. check frameData[0]
        byte hb1 = chunkData.get();
        
        // 2. No check FIN. because FIN bit permit 0 or 1.
        boolean fragmented = (hb1 & FIN_MASK) == 0;
        
        // 3. check reserve field.
        if ((hb1 & (RSV1_MASK | RSV2_MASK | RSV3_MASK)) != 0) {
            throw new IllegalArgumentException("Found nonzero bit in reserve field. (RSV1,2,3)");
        }
        
        // 4. check opcode
        int opcodeNum = hb1 & OPCODE_MASK;
        Opcode opcode = Opcode.valueOf(opcodeNum);
        if (opcode == null) {throw new IllegalArgumentException("Found illegal opcode " + opcodeNum + ".");}
        
        // 5 check frameData[1]
        byte hb2 = chunkData.get();
        
        // 6 check reserve field.
        if ((hb2 & MASK_MASK) != 0) {throw new IllegalArgumentException("Found mask bit field. (MASK)");}
        
        // 7 check payload len
        byte payloadLength1 = (byte) (hb2 & PAYLOAD_LEN_MASK);
        PayloadLengthType payloadLengthType = PayloadLengthType.valueOf(payloadLength1);
        if (payloadLengthType == null) {
            throw new IllegalArgumentException("Found illegal payload length " + payloadLength1 + ".");
        }
    
        if (length < 2 + payloadLengthType.offset()) {return null;}
        
        long payloadLength2 = payloadLength1;
        switch (payloadLengthType) {
            case LEN_16:
                payloadLength2 = ((chunkData.get() & 0xFF) << 8) | (chunkData.get() & 0xFF);
                break;
            case LEN_63:
                payloadLength2 = 0x7FFFFFFFFFFFFFFFL & chunkData.getLong();
                break;
            default:
                break;
        }
        if (payloadLength2 > Integer.MAX_VALUE) {throw new IllegalArgumentException("large data is not support yet");}
        
        // if current is continuation then use previous opcode.
        if (prevHeader != null && Opcode.CONTINUATION.equals(opcode)) {opcode = prevHeader.getOpcode();}
        
        return parseHeader(fragmented, opcode, length, payloadLength2, payloadLengthType);
    }
}
